package qq2564874169.tuaotuao.fx.app;


import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import qq2564874169.tuaotuao.fx.FxDate;
import qq2564874169.tuaotuao.fx.utils.StringUtil;

import java.util.UUID;

public abstract class RequestAuth {
    private static String userkey = UUID.randomUUID().toString();
    private final String split = "_";
    private String authName;
    private String secret;
    private int logoutWhenIdle;
    private boolean fromQuery;

    public String getAuthName() {
        return authName;
    }

    public RequestAuth withAuthName(String authName) {
        this.authName = authName;
        return this;
    }

    public RequestAuth withSecret(String secret) {
        this.secret = secret;
        return this;
    }

    public RequestAuth withLogoutWhenIdle(int seconds) {
        logoutWhenIdle = seconds;
        return this;
    }

    public RequestAuth withReadFromQuery(boolean b) {
        fromQuery = b;
        return this;
    }

    public RequestAuth(MvcEvents mvcEvents) {
        mvcEvents.evActionBefore.add((t, e) -> before(e.request));
        mvcEvents.evActionAfter.add((t, e) -> after(e.request, e.response));
        withAuthName("authtoken").withSecret("secret").withLogoutWhenIdle(60 * 10);
    }

    public static RequestUser getUser(ServletRequest request) {
        return (RequestUser) request.getAttribute(userkey);
    }

    protected abstract RequestAuthMapper getMapper(HttpServletRequest request);

    private void setUser(HttpServletRequest request, RequestUser user) {
        request.setAttribute(userkey, user);
    }

    private String readTokenFromCookie(HttpServletRequest request) {
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                if (authName.equalsIgnoreCase(cookie.getName())) {
                    String value = cookie.getValue();
                    return StringUtil.hasValue(value) ? value : null;
                }
            }
        }
        return null;
    }

    private String readTokenFromHeader(HttpServletRequest request) {
        String value = request.getHeader(authName);
        return StringUtil.hasValue(value) ? value : null;
    }

    private String readTokenFromQuery(HttpServletRequest request) {
        String value = request.getParameter(authName);
        return StringUtil.hasValue(value) ? value : null;
    }

    protected AuthParam resolveToken(String token) {
        String[] items = token.split(split);
        if (items.length != 4) {
            return null;
        }
        String uid = items[0];
        String isabs = items[1];
        String stime = items[2];
        String sign = items[3];
        AuthParam param = new AuthParam();
        param.userId = uid;
        param.isAbsTime = "1".equals(isabs);
        param.expire = Long.parseLong(stime);
        param.sign = sign;
        return param;
    }

    protected String getSecret(AuthParam param) {
        return secret;
    }

    private AuthParam checkToken(String token) {
        AuthParam param = resolveToken(token);
        if (param == null) {
            return null;
        }
        if (param.getSign(getSecret(param)).equalsIgnoreCase(param.sign) == false) {
            return null;
        }
        param.token = token;
        return param;
    }

    private void before(HttpServletRequest request) {
        AuthParam param = null;
        String token = readTokenFromHeader(request);
        if (StringUtil.hasValue(token)) {
            param = checkToken(token);
            if (param != null) {
                param.fromHeader = true;
            }
        }
        if (param == null && fromQuery) {
            token = readTokenFromQuery(request);
            if (StringUtil.hasValue(token)) {
                param = checkToken(token);
            }
        }
        if (param == null) {
            token = readTokenFromCookie(request);
            if (StringUtil.hasValue(token)) {
                param = checkToken(token);
                if (param != null) {
                    param.fromCookie = true;
                }
            }
        }
        if (param == null) {
            return;
        }
        if (param.expire * FxDate.SECONDS > System.currentTimeMillis()) {
            param.url = request.getRequestURI();
            RequestUser user = createUser(request, param);
            if (user != null) {
                user.param = param;
                setUser(request, user);
            }
        }
    }

    protected RequestUser createUser(HttpServletRequest request, AuthParam param) {
        try (RequestAuthMapper mapper = getMapper(request)) {
            if (mapper != null) {
                return mapper.getUser(param);
            }
        }
        return null;
    }

    protected String createToken(AuthParam param) {
        String sign = param.getSign(getSecret(param));
        return StringUtil.join(split, param.userId, param.isAbsTime ? "1" : "0", param.expire, sign);
    }

    private int getMaxAge(RequestUser user) {
        long time = user.param.expire * FxDate.SECONDS - System.currentTimeMillis();
        if (time > Integer.MAX_VALUE) {
            return Integer.MAX_VALUE;
        }
        return (int) time;
    }

    private void after(HttpServletRequest request, HttpServletResponse response) {
        RequestUser user = getUser(request);
        if (user == null) {
            return;
        }
        if (user.isLogout) {
            if (user.param.fromHeader) {
                response.setHeader(authName, "");
            } else if (user.param.fromCookie) {
                Cookie cookie = new Cookie(authName, "");
                cookie.setPath("/");
                cookie.setMaxAge(0);
                response.addCookie(cookie);
            }
            return;
        }
        if (user.param.isAbsTime == false) {
            user.param.expire = (System.currentTimeMillis() / FxDate.SECONDS) + logoutWhenIdle;
        }
        if (user.param.isLogin) {
            user.param.token = createToken(user.param);
            Cookie cookie = new Cookie(authName, user.param.token);
            cookie.setPath("/");
            cookie.setMaxAge(getMaxAge(user));
            response.addCookie(cookie);
            response.setHeader(authName, user.param.token);
            return;
        } else if (user.param.isAbsTime) {
            return;
        } else {
            user.param.token = createToken(user.param);
        }
        if (user.param.fromHeader) {
            response.setHeader(authName, user.param.token);
        } else if (user.param.fromCookie) {
            Cookie cookie = new Cookie(authName, user.param.token);
            cookie.setPath("/");
            cookie.setMaxAge(getMaxAge(user));
            response.addCookie(cookie);
        }
    }

    public RequestUser login(HttpServletRequest request, String userId, Integer absoluteExpiration, Object state) {
        if (userId.contains(split)) {
            throw new RuntimeException("userId中不能包含: '" + split + "'");
        }
        AuthParam param = new AuthParam();
        param.userId = userId;
        param.isAbsTime = absoluteExpiration != null;
        int ts = param.isAbsTime ? absoluteExpiration : logoutWhenIdle;
        param.expire = System.currentTimeMillis() / FxDate.SECONDS + ts;
        param.isLogin = true;
        param.state = state;
        param.url = request.getRequestURL().toString();
        param.token = createToken(param);

        RequestUser user = createUser(request, param);
        if (user != null) {
            user.isLogin = true;
            user.param = param;
            setUser(request, user);
        }
        return user;
    }

    public RequestUser login(HttpServletRequest request, String userId, Object state) {
        return login(request, userId, 0, state);
    }

    public RequestUser login(HttpServletRequest request, String userId) {
        return login(request, userId, null, null);
    }

    public void logout(HttpServletRequest request) {
        RequestUser user = getUser(request);
        if (user != null) {
            user.isLogout = true;
        }
    }
}
